/*
 * QEMU System Emulator
 *
 * Copyright (c) 2003-2008 Fabrice Bellard
 * Copyright (c) 2009 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "net/tap.h"
#include "net/arp/arp.h"

#include "config-host.h"

#include <signal.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <net/if.h>

#include "net.h"
#include "sysemu.h"
#include "qemu-char.h"
#include "qemu-common.h"

#include "net/tap-linux.h"

/* Maximum GSO packet size (64k) plus plenty of room for
 * the ethernet and virtio_net headers
 */

static int launch_script(const char *setup_script, const char *ifname, int fd);

static int tap_can_send(void *opaque);
static void tap_send(void *opaque);
static void tap_writable(void *opaque);

static void tap_update_fd_handler(TAPState *s)
{
	qemu_set_fd_handler2(s->fd, s->read_poll ? tap_can_send : NULL,
			s->read_poll ? tap_send : NULL,
			s->write_poll ? tap_writable : NULL, s);
}

// Turn read poll on or off by argument enable.
static void tap_read_poll(TAPState *s, int enable)
{
	// set read_poll, !!enable ensures the value is either 0 or 1.
	s->read_poll = !!enable;
	tap_update_fd_handler(s);
}

// Turn write poll on or off by argument enable.
static void tap_write_poll(TAPState *s, int enable)
{
	s->write_poll = !!enable;
	tap_update_fd_handler(s);
}

// TODO: Temporarily unknown.
static void tap_writable(void *opaque)
{
	TAPState *s = opaque;

	// Set write poll disable.
	tap_write_poll(s, 0);

	qemu_flush_queued_packets(&s->nc);
}

// Write data to the outside of the vm or to real network.
// Receive data from outside of the real network.
// This has similar function with system call: send().
// Data flow: Client -> [Net Adapter] -> outside.
static ssize_t tap_write_packet(TAPState *s, const struct iovec *iov,
		int iovcnt)
{
	ssize_t len;

	// EINTR: Interrupted system call.
	do
	{
		// TODO: generate reply for all ARP requests.
		//if(reply_arp_v(s, iov, iovcnt))
		//	break;
		// Write "iovcnt" iovs to TAP, if system call is interrupted, then
		// try again.
		len = writev(s->fd, iov, iovcnt);
	} while (len == -1 && errno == EINTR);

	// EAGAIN: Try again.
	if (len == -1 && errno == EAGAIN)
	{
		// Enable write poll.
		tap_write_poll(s, 1);
		return 0;
	}

	return len;
}

// If we receive data stream, then send it to the socket. To the outside of vm.
// Equals to send(), actual work is done by tap_write_packet().
static ssize_t tap_receive_iov(VLANClientState *nc, const struct iovec *iov,
		int iovcnt)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);
	const struct iovec *iovp = iov;
	struct iovec iov_copy[iovcnt + 1];
	struct virtio_net_hdr hdr =
	{ 0, };

	// If vnet hdr exists but not in use then make a copy and call write_package,
	// or else call write_package directly.
	if (s->has_vnet_hdr && !s->using_vnet_hdr)
	{
		iov_copy[0].iov_base = &hdr;
		iov_copy[0].iov_len = sizeof(hdr);
		// copy the data from "iovec *iov" to iov_copy
		memcpy(&iov_copy[1], iov, iovcnt * sizeof(*iov));
		iovp = iov_copy;
		iovcnt++;
	}

	// Write to file /dev/net/tun
	return tap_write_packet(s, iovp, iovcnt);
}

// Similar with the above one, except that the data type is (char *)
static ssize_t tap_receive_raw(VLANClientState *nc, const uint8_t *buf,
		size_t size)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);
	struct iovec iov[2];
	int iovcnt = 0;
	struct virtio_net_hdr hdr =
	{ 0, };

	if (s->has_vnet_hdr)
	{
		iov[iovcnt].iov_base = &hdr;
		iov[iovcnt].iov_len = sizeof(hdr);
		iovcnt++;
	}

	iov[iovcnt].iov_base = (char *) buf;
	iov[iovcnt].iov_len = size;
	iovcnt++;

	return tap_write_packet(s, iov, iovcnt);
}

// A more elegant way to send data: (1) If vnet hdr is not in use, send it; or (2)
// wait until vnet hdr is not busy.
// It seems that this function is not in use.
static ssize_t tap_receive(VLANClientState *nc, const uint8_t *buf, size_t size)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);
	struct iovec iov[1];

	if (s->has_vnet_hdr && !s->using_vnet_hdr)
	{
		return tap_receive_raw(nc, buf, size);
	}

	iov[0].iov_base = (char *) buf;
	iov[0].iov_len = size;

	return tap_write_packet(s, iov, 1);
}

// Judge if the tap can send any data.
static int tap_can_send(void *opaque)
{
	TAPState *s = opaque;

	return qemu_can_send_packet(&s->nc);
}

#ifndef __sun__
ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen)
{
	return read(tapfd, buf, maxlen);
}
#endif

// Set read poll enabled after all data are sent.
void tap_send_completed(VLANClientState *nc, ssize_t len)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);
	// set read poll enabled.
	tap_read_poll(s, 1);
}

// The adapter has received data from the net.
// Similar function with system call: recv().
// Data Flow: Outside -> [Net Adapter] -> Client.
static void tap_send(void *opaque)
{
	TAPState *s = opaque;
	int size;

	// If there are data to send, then send them until all are sent.
	do
	{
		uint8_t *buf = s->buf;

		// Read data from /dev/net/tun.
		size = tap_read_packet(s->fd, s->buf, sizeof(s->buf));
		if (size <= 0)
		{
			break;
		}

		// if vnet hdr exists, and is not in use (not busy).
		if (s->has_vnet_hdr && !s->using_vnet_hdr)
		{
			buf += sizeof(struct virtio_net_hdr);
			size -= sizeof(struct virtio_net_hdr);
		}

		// data starts from buf (address) and of size "size".
		// Send all data to tap and enable send poll again to wait for new data.
		size = qemu_send_packet_async(&s->nc, buf, size, tap_send_completed);

		if (size == 0)
		{
			// No data are sent, disable read poll.
			tap_read_poll(s, 0);
		}
	} while (size > 0 && qemu_can_send_packet(&s->nc));
}

// En? What is UFO?
int tap_has_ufo(VLANClientState *nc)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);

	assert(nc->info->type == NET_CLIENT_TYPE_TAP);

	return s->has_ufo;
}

int tap_has_vnet_hdr(VLANClientState *nc)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);

	assert(nc->info->type == NET_CLIENT_TYPE_TAP);

	return s->has_vnet_hdr;
}

// Set vnet busy.
void tap_using_vnet_hdr(VLANClientState *nc, int using_vnet_hdr)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);

	using_vnet_hdr = using_vnet_hdr != 0;

	assert(nc->info->type == NET_CLIENT_TYPE_TAP);
	assert(s->has_vnet_hdr == using_vnet_hdr);

	s->using_vnet_hdr = using_vnet_hdr;
}

void tap_set_offload(VLANClientState *nc, int csum, int tso4, int tso6,
		int ecn, int ufo)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);

	return tap_fd_set_offload(s->fd, csum, tso4, tso6, ecn, ufo);
}

// Close tap network.
static void tap_cleanup(VLANClientState *nc)
{
	TAPState *s = DO_UPCAST(TAPState, nc, nc);

	qemu_purge_queued_packets(nc);

	// Execute down script
	if (s->down_script[0])
		launch_script(s->down_script, s->down_script_arg, s->fd);

	// disable read poll and write poll.
	tap_read_poll(s, 0);
	tap_write_poll(s, 0);

	// close file /dev/net/tun.
	close(s->fd);
}

/* fd support */

static NetClientInfo net_tap_info =
{ .type = NET_CLIENT_TYPE_TAP, .size = sizeof(TAPState),
		.receive = tap_receive, .receive_raw = tap_receive_raw,
		.receive_iov = tap_receive_iov, .cleanup = tap_cleanup, };

// Setup TAPState, prepare for read data from iov.
static TAPState *net_tap_fd_init(VLANState *vlan, const char *model,
		const char *name, int fd, int vnet_hdr)
{
	VLANClientState *nc;
	TAPState *s;

	nc = qemu_new_net_client(&net_tap_info, vlan, NULL, model, name);

	s = DO_UPCAST(TAPState, nc, nc);

	s->fd = fd;
	s->has_vnet_hdr = vnet_hdr != 0;
	s->using_vnet_hdr = 0;
	s->has_ufo = tap_probe_has_ufo(s->fd);
	tap_set_offload(&s->nc, 0, 0, 0, 0, 0);
	// set read poll enabled.
	tap_read_poll(s, 1);
	return s;
}

// execute scripts, one by one.
static int launch_script(const char *setup_script, const char *ifname, int fd)
{
	sigset_t oldmask, mask;
	int pid, status;
	char *args[3];
	char **parg;

	sigemptyset(&mask);
	sigaddset(&mask, SIGCHLD);
	sigprocmask(SIG_BLOCK, &mask, &oldmask);

	/* try to launch network script */
	pid = fork();
	if (pid == 0)
	{
		int open_max = sysconf(_SC_OPEN_MAX), i;

		for (i = 0; i < open_max; i++)
		{
			if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO
					&& i != fd)
			{
				close(i);
			}
		}
		parg = args;
		*parg++ = (char *) setup_script;
		*parg++ = (char *) ifname;
		*parg++ = NULL;
		execv(setup_script, args);
		_exit(1);
	}
	else if (pid > 0)
	{
		while (waitpid(pid, &status, 0) != pid)
		{
			/* loop */
		}
		sigprocmask(SIG_SETMASK, &oldmask, NULL);

		if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
		{
			return 0;
		}
	}
	fprintf(stderr, "%s: could not launch network script\n", setup_script);
	return -1;
}

// Initial net tap, the start of tap device.
static int net_tap_init(QemuOpts *opts, int *vnet_hdr)
{
	int fd, vnet_hdr_required;
	char ifname[128] =
	{ 0, };
	const char *setup_script;

	if (qemu_opt_get(opts, "ifname"))
	{
		pstrcpy(ifname, sizeof(ifname), qemu_opt_get(opts, "ifname"));
	}

	*vnet_hdr = qemu_opt_get_bool(opts, "vnet_hdr", 1);
	if (qemu_opt_get(opts, "vnet_hdr"))
	{
		vnet_hdr_required = *vnet_hdr;
	}
	else
	{
		vnet_hdr_required = 0;
	}

	TFR(fd = tap_open(ifname, sizeof(ifname), vnet_hdr, vnet_hdr_required));
	if (fd < 0)
	{
		return -1;
	}

	setup_script = qemu_opt_get(opts, "script");
	if (setup_script && setup_script[0] != '\0' && strcmp(setup_script, "no")
			!= 0 && launch_script(setup_script, ifname, fd))
	{
		close(fd);
		return -1;
	}

	qemu_opt_set(opts, "ifname", ifname);

	printf("Tap started:\nifname=%s\nvnet_hdr=%d\n\n", ifname, *vnet_hdr);

	return fd;
}

int net_init_tap(QemuOpts *opts, Monitor *mon, const char *name,
		VLANState *vlan)
{
	TAPState *s;
	int fd, vnet_hdr = 0;

	if (qemu_opt_get(opts, "fd"))
	{
		if (qemu_opt_get(opts, "ifname") || qemu_opt_get(opts, "script")
				|| qemu_opt_get(opts, "downscript") || qemu_opt_get(opts,
				"vnet_hdr"))
		{
			qemu_error(
					"ifname=, script=, downscript= and vnet_hdr= is invalid with fd=\n");
			return -1;
		}

		fd = net_handle_fd_param(mon, qemu_opt_get(opts, "fd"));
		if (fd == -1)
		{
			return -1;
		}

		fcntl(fd, F_SETFL, O_NONBLOCK);

		vnet_hdr = tap_probe_vnet_hdr(fd);
	}
	else
	{
		if (!qemu_opt_get(opts, "script"))
		{
			qemu_opt_set(opts, "script", DEFAULT_NETWORK_SCRIPT);
		}

		if (!qemu_opt_get(opts, "downscript"))
		{
			qemu_opt_set(opts, "downscript", DEFAULT_NETWORK_DOWN_SCRIPT);
		}

		fd = net_tap_init(opts, &vnet_hdr);
		if (fd == -1)
		{
			return -1;
		}
	}

	s = net_tap_fd_init(vlan, "tap", name, fd, vnet_hdr);
	if (!s)
	{
		close(fd);
		return -1;
	}

	if (tap_set_sndbuf(s->fd, opts) < 0)
	{
		return -1;
	}

	if (qemu_opt_get(opts, "fd"))
	{
		snprintf(s->nc.info_str, sizeof(s->nc.info_str), "fd=%d", fd);
	}
	else
	{
		const char *ifname, *script, *downscript;

		ifname = qemu_opt_get(opts, "ifname");
		script = qemu_opt_get(opts, "script");
		downscript = qemu_opt_get(opts, "downscript");

		snprintf(s->nc.info_str, sizeof(s->nc.info_str),
				"ifname=%s,script=%s,downscript=%s", ifname, script, downscript);

		if (strcmp(downscript, "no") != 0)
		{
			snprintf(s->down_script, sizeof(s->down_script), "%s", downscript);
			snprintf(s->down_script_arg, sizeof(s->down_script_arg), "%s",
					ifname);
		}
	}

	printf("int net_init_tap()\n\n");
	return 0;
}
